我們在最一開始的時候,就已經看過最基本用來管理state的hook-useState了,今天再來看看另一個也是用在管理state的hook,就是useReducer。「useReducer」的用途類似useState,一樣也是進行狀態管理的hook,雖然在vue框架中,並沒有提供這樣的用法,但是和Vuex這類的狀態管理工具的模式有點類似。不過今天就不看Vue了,而是會專注在React的useReducer的用法。
正式提到useReducer之前,我們先快速透過一個實際的例子來看useState的使用方式。這個例子是「畫面中有+1和-1的按鈕,可以對count增加一和減少一」。
如果是透過setState來進行的話,會需要寫兩個函式來進行加一和減一的操作
。這只是一個很簡單的例子,在大多數情況下,幾乎都會利用useState去實作它。
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<div className="App">
<div>
<h2>Counter</h2>
<p>current count: {count}</p>
<div className='buttons'>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
</div>
</div>
);
}
export default App;
前面快速回顧useState後,這裡再來看看如果是useReducer的話,上面同樣的例子會變成怎麼寫呢?想要使用useReducer,通常會有以下三個步驟:
1. 定義reducer function
reducer function會定義對state操作的action,在這個例子裡的action也就是加一
和減一
。在這個reducer function裡面會帶入兩個參數,分別是state
和action
。
// 在這個例子中,就是如果動作是增加,就把count state +1,如果是減少,就把count state -1。
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
};
2. 使用useReducer宣告state和dispatch
在這個步驟中,會需要把剛剛定義好的reducer function和state初始值帶進去useReducer裡面。使用useReducer的時候,和使用useState的時候一樣,會回傳一個陣列,這裡一樣會透過解構的方式,把state和更新state的函式給取出,在useReducer中,這個更新state的函式通常會被稱作是
dispatch
。
const [state, dispatch] = useReducer(reducer, { count: 0 });
3. 透過dispatch函式進行state的操作
在使用useReducer進行state的更新時,會需要用useReducer回傳的dispatch函式,另外需要帶上要進行的action,才能進行對應的state操作。在這裡傳入的action通常都是是一個內含type key的物件
。
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
整個完成後,就可以從畫面來進行state的操作了。
這時候可能有些人會有個疑問,那就是前面的例子用useState也可以行得通,而且useState用好好的,為什麼還要使用useReducer呢?可以觀察一下操作state的動作這部分,在程式碼上的差異。
// 使用useState時,定義更新state動作的寫法
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
// 使用useReducer時,定義更新state動作的寫法
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
};
的確在許多情境中,使用useState就可以了,但是如果有仔細觀察的話,應該不難發現剛剛兩種寫法中的差異,在useState的寫法中,想要操作state的function需要額外散著寫,當邏輯一複雜,或是元件中還有其他state存在的時候,針對相關程式碼的管理就會比較費工。這個時候如果改成使用useReducer,就能把跟這個state相關的所有操作都歸納在一起,不僅一目瞭然,如果需要調整的時候,也不用找好幾個地方修改,因為跟這個state相關的action都放在同一個reducer,需要使用時,也能很語意化地指定我要做什麼動作,讓reducer自己去對到符合的state操作。
useState之所以可以用來管理與畫面渲染有關的state,是因為它會回傳setState的函式,這個函式能夠提醒React我們正在對state進行改動的動作,才能進一步地比較新的state和舊的state是否有變動,再進到呼叫component function的階段。而useReducer的話,則是透過dispatch函式
來通知React我們正在進行state的更動,再來也會經歷比對新舊的state,來決定是否要呼叫component function。
雖然useReducer和useState一樣都是在操作state,但是使用useReducer的方式能讓操作state的動作變得更清楚易懂,如果邏輯的基底有很複雜的內容,那用useReducer就能讓程式碼更好管理,因為狀態邏輯能被抽出來。當然如果是一個很單純的state操作,使用useReducer反而會讓使用變得複雜化。在實務中,還是得依照實際的情境下去選擇要用useState還是useReducer,並沒有哪個比較好,或是哪個比較不好,只有適不適合當下的實作內容。
Extracting State Logic into a Reducer